`include "../../include/cpu_defines.sv"

module BranchIssueQueue
#(parameter BUFFER_SIZE=8)(
	input logic cpu_clk,
	input logic cpu_rst_n,
	input logic en1,
	input logic en2,
	input logic [5: 0] rob_index1,
	input logic [5: 0] rob_index2,
	input logic [31: 0] real_addr1,
	input logic [31: 0] real_addr2,
	input logic wb_en1,
	input logic wb_en2,
	input logic [5: 0] wb_rob_index1,
	input logic [5: 0] wb_rob_index2,
	input logic [31: 0] wb_ans1,
	input logic [31: 0] wb_ans2,
	output logic out_en1,
	output logic out_en2,
	output logic [31: 0] out_addr1,
	output logic [31: 0] out_addr2,
	output logic out_branch1,
	output logic out_branch2,
	output logic [5: 0] out_rob_index1,
	output logic [5: 0] out_rob_index2,
	output logic dis_branch_stall_req
);

	typedef struct packed{
		logic en, branch;
		logic [5: 0] rob_index;
		logic [31: 0] addr;
	} branch_buffer_t;
	branch_buffer_t buffer[BUFFER_SIZE-1: 0];
	logic [1: 0] add_num, sub_num;
	logic [$clog2(BUFFER_SIZE)-1: 0] head, tail, headp1, tailp1;
	logic [$clog2(BUFFER_SIZE): 0] remain_count;
	logic [$clog2(BUFFER_SIZE)-1: 0] select_index1, select_index2;
	logic hit1, hit2;

	assign add_num[1] = en1 & en2;
	assign add_num[0] = en1 ^ en2;
	assign sub_num[1] = out_en1 & out_en2;
	assign sub_num[0] = out_en1 ^ out_en2;
	assign headp1 = head + 1;
	assign tailp1 = tail + 1;
	assign dis_branch_stall_req = remain_count < add_num;
	always_comb begin
		if(buffer[tail].rob_index == search_rob_index1 || buffer[tail].rob_index == search_rob_index2)begin
			hit1 = 1'b1;
		end
		else begin
			hit1 = 1'b0;
		end

		if(buffer[tailp1].rob_index == search_rob_index1 || buffer[tailp1].rob_index == search_rob_index2)begin
			hit2 = 1'b1;
		end
		else begin
			hit2 = 1'b0;
		end
	end
	assign out_en1 = hit1;
	assign out_en2 = hit2;
	assign out_addr1 = buffer[tail].addr;
	assign out_addr2 = buffer[tailp1].addr;
	assign out_branch1 = buffer[tail].branch;
	assign out_branch2 = buffer[tailp1].branch;
	assign out_rob_index1 = buffer[tail].rob_index;
	assign out_rob_index2 = buffer[tailp1].rob_index;



	always_ff @(posedge cpu_clk)begin
		if(cpu_rst_n == 1'b0)begin
			for(int i=0; i<BUFFER_SIZE; i++)begin
				buffer[i] <= 0;
				free_list[i] <= i;
			end
			head <= 0;
			tail <= 0;
			remain_count <= BUFFER_SIZE;
		end
		else begin
			remain_count <= remain_count + (dis_branch_stall_req ? 0 : add_num) - sub_num;
			tail <= tail + sub_num;
			if(hit1)begin
				buffer[select_index1].en <= 1'b0;
			end

			if(hit2)begin
				buffer[select_index2].en <= 1'b0;
			end

			if(hit1 && hit2)begin
				free_list[tail] <= select_index1;
				free_list[tail + 1] <= select_index2;
			end
			else if(hit1)begin
				free_list[tail] <= select_index1;
			end
			else if(hit2)begin
				free_list[tail] <= select_index2;
			end
			if(wb_en1)begin
				for(int i=0; i<BUFFER_SIZE; i++)begin
					if(buffer[i].rob_index == wb_rob_index1)begin
						buffer[i].branch <= wb_ans1[0];
					end

				end
			end
			if(wb_en2)begin
				for(int i=0; i<BUFFER_SIZE; i++)begin
					if(buffer[i].rob_index == wb_rob_index2)begin
						buffer[i].branch <= wb_ans2[0];
					end

				end
			end


			if(!dis_branch_stall_req)begin
				head<= head + add_num;
				if(en1 && en2)begin
					buffer[head].en <= 1'b1;
					buffer[head].real_addr <= real_addr1;
					buffer[head].rob_index <= rob_index1;
					buffer[headp1].en <= 1'b1;
					buffer[headp1].real_addr <= real_addr2;
					buffer[headp1].rob_index <= rob_index2;					
				end
				else if(en1)begin
					buffer[head].en <= 1'b1;
					buffer[head].real_addr <= real_addr1;
					buffer[head].rob_index <= rob_index1;
				end
				else if(en2)begin
					buffer[head].en <= 1'b1;
					buffer[head].real_addr <= real_addr2;
					buffer[head].rob_index <= rob_index2;
				end
			end
		end
	end

endmodule